#ifndef CSVREADER_HPP_
#define CSVREADER_HPP_

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <assert.h>
#include <math.h>

#include "Logger.hpp"
#include "Mapper.hpp"
#include "File.hpp"

/*
 * Reads data from input file, parses strings and stores ints into new file "tmpN"
 */
class CsvReader {
private:
	File rFile, wFile;
	unsigned int nChunks, chunkCounter;

	char remBuf[30];		//Remainder of number from previous page
	unsigned int remSize;			//Amount of stored digits

	bool comma;
public:
	CsvReader(char *path) {
		assert(0 == chunkSize % pageSize);
		
		rFile.setPath(path);
		rFile.Open(File::MODE_READ);

		if (0 == rFile.size) {
			Logger::getInstance().log(Logger::INFO, "There is nothing to sort.\n");
			exit(0);
		}
		
		nChunks = ceil((double)rFile.size/chunkSize);
		if (nChunks > maxChunks) {
			Logger::getInstance().log(Logger::ERROR, "File is too large. Max size of file is %d bytes\n", maxChunks*chunkSize);
			exit(1);
		}
		chunkCounter = 0;
		remSize = 0;

		comma = 1;
	}
	
	~CsvReader() {
		if (rFile.isMapped) {
			Mapper::unmap(rFile);
		}
		if (rFile.isOpened) {
			rFile.Close();
		}
		if (wFile.isMapped) {
			Mapper::unmap(wFile);
		}
		if (wFile.isOpened) {
			wFile.Close();
		}
	}

	void read() {
		while (hasNext()) {
			next();
		}
	}

	bool hasNext() {
		assert(rFile.isOpened);
		assert(chunkCounter <= nChunks);
		if (chunkCounter == nChunks) {
			return 0;
		} else {
			return 1;
		}
	}
	
	void next() {
		assert(rFile.isOpened);
		assert(hasNext());

		++chunkCounter;

		prepareRFile();
		prepareWFile();

		parse();

		Mapper::unmap(wFile);
		wFile.Close();
	}
private:	
	void prepareWFile() {
		char buf[20];
		snprintf(buf,20, "tmp%d", chunkCounter);
		wFile.setPath(buf);
		wFile.Open(File::MODE_RW);
		wFile.mSize = (rFile.mSize/2 + 2)*sizeof(int);
		Mapper::map(wFile, Mapper::MODE_WRITE);
	}

	void prepareRFile() {
		if (rFile.isMapped) {
			Mapper::unmap(rFile);
		}		
		if (1 == nChunks) {
			rFile.mSize = rFile.size;
		} else {
			rFile.mSize = std::min(chunkSize, rFile.size - rFile.offset);
		}
		Mapper::map(rFile, Mapper::MODE_READ);
	}

	void parse() {
		assert(rFile.isOpened);
		assert(rFile.isMapped);
		assert(wFile.isOpened);
		assert(wFile.isMapped);

		rFile.cSize = rFile.offset - rFile.mSize; 

		combinePartitionedNumber();

		while(rFile.cSize < rFile.offset) {
			parseNumber();
		}

		saveLastDigitsOfChunk();
		if (chunkCounter == nChunks) {
			flushRemBuf();
		}		

		if (-1 == ftruncate(wFile.fd,wFile.cSize)) {	
			Logger::getInstance().log(Logger::ERROR, "Ftruncate of file *%s* failed.\n", wFile.path);
			exit(1);
		}
	}
	
	/*
	 * Combines two parts of one number: from previous and current chunk.
	 */
	void combinePartitionedNumber() {
		unsigned int i;
		if (remSize > 0) {
			for (i = 0; i < rFile.mSize; ++i) {
				++rFile.cSize;
				if (*rFile.cPtr == ',' || *rFile.cPtr == '\n' || *rFile.cPtr == '\t' || *rFile.cPtr == ' ') {
					++rFile.cPtr;
					break;
				}
				++rFile.cPtr;
			}
			if (i != 0) {
				strncpy(&(remBuf[remSize]), rFile.cPtr, i);
			}
			strncpy(&(remBuf[remSize+i]), "\n", 1);

			*(int*)wFile.cPtr = strtol (remBuf,NULL,10);
			if (ERANGE == errno) {
				Logger::getInstance().log(Logger::ERROR, "One of the numbers is out of range of representable values by int.\n");
				exit(1);
			}
			wFile.cPtr += sizeof(int);
			wFile.cSize += sizeof(int);
			remSize = 0;
		}
	}

	void parseNumber() {
		if ((('0' <= *rFile.cPtr) && (*rFile.cPtr <= '9')) || '-' == *rFile.cPtr) {
			if ( ! comma) {
				Logger::getInstance().log(Logger::ERROR, "Two ints are not separated with comma.");
				exit(1);
			}
			if ('-' == *rFile.cPtr && remSize != 0) {
				Logger::getInstance().log(Logger::ERROR, "Symbol *-* is not in the beginning of number.\n");
				exit(1);
			}		
			strncpy(&(remBuf[remSize]), rFile.cPtr, 1);
			++rFile.cPtr;
			++rFile.cSize;
			++remSize;
		} else {
			if (*rFile.cPtr != ',' && *rFile.cPtr != '\n' && *rFile.cPtr != '\t' && *rFile.cPtr != ' ') {
				Logger::getInstance().log(Logger::ERROR, "Symbol *%c* is not integer, new line, comma or space.\n", *rFile.cPtr);
				exit(1);
			}
			if (remSize > 0) {
				strncpy(&(remBuf[remSize]), "\n", 1);
				*(int*)wFile.cPtr = strtol (remBuf,NULL,10);
				if (ERANGE == errno) {
					Logger::getInstance().log(Logger::ERROR, "One of the numbers is out of range of representable values by int.\n");
					exit(1);
				}
				wFile.cPtr += sizeof(int);
				wFile.cSize += sizeof(int);
				remSize = 0;
				if (',' == *rFile.cPtr) comma = 1;
				else comma = 0;
			} else if (',' == *rFile.cPtr) {
				if (comma) {
					Logger::getInstance().log(Logger::ERROR, "Two commas in a row.");
					exit(1);
				} else comma = 1;
			}
			++rFile.cPtr;
			++rFile.cSize;
		}
	}
	
	void saveLastDigitsOfChunk() {
		register unsigned int i;
		if (('0' <= *(char*)rFile.cPtr)&& (*(char*)rFile.cPtr <= '9')) {
			wFile.cPtr -= sizeof(int);
			wFile.cSize -= sizeof(int);
			for (i = 0; i < sizeof(remBuf); ++i) {
				if (('0' <= *rFile.cPtr)&& (*rFile.cPtr <= '9')) {
					--rFile.cPtr;
					++i;
				} else break;
			}
			remSize = i;
			++rFile.cPtr;
			strncpy(remBuf, rFile.cPtr, remSize);
		}	
	}

	void flushRemBuf() {
		if (remSize > 0) {
			strncpy(&(remBuf[remSize]), "\n", 1);
			*(int*)wFile.cPtr = atoi(remBuf);
			wFile.cPtr += sizeof(int);
			wFile.cSize += sizeof(int);
			remSize = 0;
		}
	}
public:
	unsigned int getNChunks() {
		return nChunks;
	}
};

#endif /* CSVREADER_HPP_ */
